mod expression;
mod program;
mod statement;

use std::{
    fs,
    path::{Path, PathBuf},
};

use crate::{
    analyzer::program::ProgramCompilerUnit,
    data::{KeywordType, SymbolType},
    error::{CompilerError, CompilerResult},
    token::{Token, Tokenizer},
    util,
};
use anyhow::Result;
/// Compiler unit
pub(crate) struct Analyzer;

impl Analyzer {
    /// compile the jack file or path to grammar xml file
    pub(crate) fn compile<P: AsRef<Path>>(jack_file: P) -> Result<Vec<PathBuf>> {
        let jack_file = jack_file.as_ref();
        let mut output_paths = Vec::new();
        if jack_file.is_dir() {
            // all the jack file path
            for file_path in jack_file.read_dir()?.flatten().filter_map(|item| {
                if item
                    .path()
                    .extension()
                    .unwrap_or_default()
                    .to_string_lossy()
                    == "jack"
                {
                    Some(item.path())
                } else {
                    None
                }
            }) {
                output_paths.push(Self::compile_file(&file_path)?);
            }
        } else {
            output_paths.push(Self::compile_file(jack_file)?);
        }
        Ok(output_paths)
    }
    /// compile the jack file to grammar xml file
    fn compile_file(jack_file: &Path) -> Result<PathBuf> {
        let translator = Tokenizer::new(jack_file)?;
        let xml_code =
            ProgramCompilerUnit::compile_class(&mut translator.tokens()?.iter().peekable())?;
        fs::write(
            &translator.output_path_grammar,
            util::format_xml(&xml_code)?,
        )?;
        Ok(translator.output_path_grammar)
    }
    /// check whether the token is a symbol type
    ///
    /// # params
    ///
    /// * token will matched token
    /// * keyword_type the matched symbol type
    ///
    /// # return
    ///
    /// return the symbol type's xml
    pub(super) fn match_symbol(token: &Token, symbol_type: SymbolType) -> CompilerResult<String> {
        if let Token::Symbol(token_symbol_type) = token
            && &symbol_type == token_symbol_type
        {
            Ok(symbol_type.to_xml())
        } else {
            Err(CompilerError::Symbol(format!(
                "not a {} symbol",
                symbol_type.char()
            )))
        }
    }

    /// check whether the token is a keyword type
    ///
    /// # params
    ///
    /// * token will matched token
    /// * keyword_type the matched keyword type
    ///
    /// # return
    ///
    /// return the keyword type's xml
    pub(super) fn match_keyword(
        token: &Token,
        keyword_type: KeywordType,
    ) -> CompilerResult<String> {
        if let Token::Keyword(token_keyword_type) = token
            && &keyword_type == token_keyword_type
        {
            Ok(keyword_type.to_xml())
        } else {
            Err(CompilerError::Symbol(format!(
                "not a {} keyword",
                keyword_type.to_str()
            )))
        }
    }
}

#[cfg(test)]
mod test;
